feat: invoice delete v3 endpoint#4654
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR adds delete-invoice support across billing validation, v3 server routing, generated API surfaces, the JavaScript SDK, and end-to-end coverage. ChangesDelete Invoice Feature
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant DeleteBillingInvoiceHandler
participant billing_service as "billing.Service"
Client->>Server: DELETE /openmeter/billing/invoices/{invoiceId}
Server->>DeleteBillingInvoiceHandler: DeleteBillingInvoice().With(invoiceId)
DeleteBillingInvoiceHandler->>billing_service: GetInvoice (expanded)
billing_service-->>DeleteBillingInvoiceHandler: invoice
DeleteBillingInvoiceHandler->>DeleteBillingInvoiceHandler: ValidateAPIInvoiceDeleteSupported(invoice)
DeleteBillingInvoiceHandler->>billing_service: DeleteInvoice(DeletionSource=APIRequest)
billing_service-->>DeleteBillingInvoiceHandler: result status
DeleteBillingInvoiceHandler-->>Client: 204 No Content or ValidationError
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR adds a v3 endpoint for deleting billing invoices. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (2): Last reviewed commit: "fix: regenerate the js client" | Re-trigger Greptile |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if existing.Type() != billing.InvoiceTypeStandard { |
There was a problem hiding this comment.
Issuing Invoices Can Be Deleted
When a standard invoice has already left draft and is in an issuing state such as issuing.syncing or issuing.charge_booking, this handler still calls DeleteInvoice because it only checks the invoice type. The billing state machine permits delete transitions from those issuing states, so this endpoint can return 204 and delete an invoice that the v3 API contract says is no longer deletable.
Prompt To Fix With AI
This is a comment left during a code review.
Path: api/v3/handlers/billinginvoices/delete.go
Line: 47
Comment:
**Issuing Invoices Can Be Deleted**
When a standard invoice has already left draft and is in an issuing state such as `issuing.syncing` or `issuing.charge_booking`, this handler still calls `DeleteInvoice` because it only checks the invoice type. The billing state machine permits delete transitions from those issuing states, so this endpoint can return 204 and delete an invoice that the v3 API contract says is no longer deletable.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fair point — the state machine owns those transition rules, so if DeleteInvoice rejects the transition for an issuing-state invoice, the handler will naturally surface that error. Adding a redundant state check here would duplicate logic that already lives in the right place.
Shall I remember this as a rule: "Do not add invoice state guards in v3 handlers; state transition validation belongs to the billing state machine"?
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
openmeter/billing/invoice_validator.go (1)
44-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMissing doc comment on exported function.
ValidateAPIGenericInvoiceDeleteSupportedis exported but has no doc comment (unlikeValidateAPIInvoiceDeleteSupportedright above it). Since this is now part of the shared public API surface (used by two call sites), a short comment would help future callers understand the usage-based-line guard without diving into the loop body.📝 Suggested doc comment
+// ValidateAPIGenericInvoiceDeleteSupported blocks deletion of invoices that +// still have active (non-deleted) usage-based charge lines, since invoice-scope +// deletion of those lines is not yet implemented. func ValidateAPIGenericInvoiceDeleteSupported(invoice GenericInvoice) error {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@openmeter/billing/invoice_validator.go` at line 44, Add a short doc comment for the exported ValidateAPIGenericInvoiceDeleteSupported function, matching the style of ValidateAPIInvoiceDeleteSupported. The comment should briefly describe that it validates invoice deletion support based on usage-derived line items and help callers understand the guard without reading the loop body. Keep the comment directly above ValidateAPIGenericInvoiceDeleteSupported so the public API is documented.e2e/billinginvoices_v3_test.go (1)
1043-1236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider adding given/when/then intent comments to the subtests.
None of the new subtests here open with a short given/when/then style comment. As per path instructions, "For service and lifecycle subtests, start each non-trivial subtest body with concise intent comments in given/when/then form." Since this test walks through the invoice delete lifecycle (create → advance → delete → repeat delete → gathering delete → unknown-id delete), it's a good fit for that convention.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/billinginvoices_v3_test.go` around lines 1043 - 1236, The new lifecycle subtests in billinginvoices_v3_test.go should start with concise given/when/then intent comments. Update each non-trivial t.Run block in this invoice delete flow—especially the customer setup, billing profile pinning, pending invoice creation, subscription/plan setup, invoice advancement, delete, repeat delete, and gathering-invoice delete cases—to add short given/when/then style comments at the top of the subtest body. Keep the comments aligned with the existing test intent and use the t.Run names and helpers like createNewBillingProfileFromDefault, CreatePendingInvoiceLineWithResponse, CreateSubscription, and DeleteBillingInvoice as anchors.Source: Path instructions
api/spec/packages/aip/src/invoices/operations.tsp (1)
138-143: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDoc comment misses the usage-based-lines rejection rule.
Per the PR notes, invoices with usage-based charge lines get rejected on delete too, not just non-draft/non-standard ones — worth calling that out here so API consumers aren't surprised by an error they can't predict from the docs alone.
As per path instructions, "The declared API should be accurate, in parity with the actual implementation, and easy to understand for the user."
📝 Suggested doc tweak
/** * Delete a billing invoice. * - * Only standard invoices in draft status can be deleted. Deleting an invoice will - * also delete all associated line items and workflow configuration. + * Only standard invoices in draft status can be deleted, and only if they do not + * contain usage-based charge lines. Deleting an invoice will also delete all + * associated line items and workflow configuration. */🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/spec/packages/aip/src/invoices/operations.tsp` around lines 138 - 143, The delete invoice doc comment on the delete operation should mention the usage-based charge line rejection rule in addition to the existing draft/standard restriction. Update the comment attached to the delete billing invoice operation in the operations definition so consumers understand that invoices containing usage-based lines are also rejected by the delete path, keeping the documentation aligned with the delete implementation and related invoice validation logic.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@api/spec/packages/aip-client-javascript/src/funcs/invoices.ts`:
- Around line 100-111: The path construction in deleteInvoice is happening
before request(), so a missing req.invoiceId can throw outside the Result
wrapper. Move the encodePath('openmeter/billing/invoices/{invoiceId}', ...) call
inside the request() callback in deleteInvoice so any invalid input is captured
consistently like the other invoice methods. Keep the http(client).delete(...)
call within that same request block using the computed path.
---
Nitpick comments:
In `@api/spec/packages/aip/src/invoices/operations.tsp`:
- Around line 138-143: The delete invoice doc comment on the delete operation
should mention the usage-based charge line rejection rule in addition to the
existing draft/standard restriction. Update the comment attached to the delete
billing invoice operation in the operations definition so consumers understand
that invoices containing usage-based lines are also rejected by the delete path,
keeping the documentation aligned with the delete implementation and related
invoice validation logic.
In `@e2e/billinginvoices_v3_test.go`:
- Around line 1043-1236: The new lifecycle subtests in
billinginvoices_v3_test.go should start with concise given/when/then intent
comments. Update each non-trivial t.Run block in this invoice delete
flow—especially the customer setup, billing profile pinning, pending invoice
creation, subscription/plan setup, invoice advancement, delete, repeat delete,
and gathering-invoice delete cases—to add short given/when/then style comments
at the top of the subtest body. Keep the comments aligned with the existing test
intent and use the t.Run names and helpers like
createNewBillingProfileFromDefault, CreatePendingInvoiceLineWithResponse,
CreateSubscription, and DeleteBillingInvoice as anchors.
In `@openmeter/billing/invoice_validator.go`:
- Line 44: Add a short doc comment for the exported
ValidateAPIGenericInvoiceDeleteSupported function, matching the style of
ValidateAPIInvoiceDeleteSupported. The comment should briefly describe that it
validates invoice deletion support based on usage-derived line items and help
callers understand the guard without reading the loop body. Keep the comment
directly above ValidateAPIGenericInvoiceDeleteSupported so the public API is
documented.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 64b72522-41dc-41df-9c6a-2a391142c544
⛔ Files ignored due to path filters (1)
api/v3/openapi.yamlis excluded by!**/openapi.yaml
📒 Files selected for processing (15)
api/spec/packages/aip-client-javascript/README.mdapi/spec/packages/aip-client-javascript/src/funcs/invoices.tsapi/spec/packages/aip-client-javascript/src/models/operations/invoices.tsapi/spec/packages/aip-client-javascript/src/models/schemas.tsapi/spec/packages/aip-client-javascript/src/sdk/invoices.tsapi/spec/packages/aip/src/invoices/operations.tspapi/v3/api.gen.goapi/v3/handlers/billinginvoices/delete.goapi/v3/handlers/billinginvoices/handler.goapi/v3/server/routes.goe2e/billinginvoices_v3_test.goe2e/v3helpers_test.goopenmeter/billing/httpdriver/invoice.goopenmeter/billing/httpdriver/invoice_test.goopenmeter/billing/invoice_validator.go
What
Adds
DELETE /api/v3/openmeter/billing/invoices/{invoiceId}— an unstable/internal/private v3 endpoint to delete a standard billing invoice.api/v3/handlers/billinginvoices/delete.go(DeleteBillingInvoice), wired intohandler.go,routes.go, and the generatedapi.gen.go/openapi.yaml.api/spec/packages/aip/src/invoices/operations.tsp, with a matchingdeletemethod on the generated JS SDK'sclient.invoices.validateAPIInvoiceDeleteSupported/validateAPIGenericInvoiceDeleteSupportedout ofopenmeter/billing/httpdriver/invoice.gointo a new exportedopenmeter/billing/invoice_validator.go(ValidateAPIInvoiceDeleteSupported/ValidateAPIGenericInvoiceDeleteSupported), so both the legacy v1 handler and the new v3 handler share the same guard.TestV3DeleteBillingInvoice(e2e) and aDeleteBillingInvoiceclient helper covering the full lifecycle: delete a draft standard invoice → 204, delete again → 404, delete a gathering invoice → 404, delete an unknown ID → 404.Why
The v3 API already supports get/list/update for billing invoices; delete was the remaining gap for basic invoice lifecycle management. The validator was pulled out to a shared, exported location instead of being duplicated, since both the v1 and v3 delete paths need to enforce the same "no usage-based charge lines" guard ahead of side-effectful line-engine cleanup.
How
ParseBody) that neither the TypeSpec nor the generated SDK ever sends for a DELETE — every real (bodyless) client call would have hitio.EOFand failed with 400. Removed the dead parsing block.GetInvoiceByIdwithoutExpand: billing.InvoiceExpandAll, soLineswas never populated and the usage-based-charge-line guard silently iterated zero lines — it would never actually block a disallowed delete. Added the missingExpand.switch/defaultbranch left over from the case where the handler still supported gathering-invoice deletion (it's now rejected earlier via an explicit type check), and added the missing TypeSpec doc comment for the new operation (the other three invoice operations all have one; this one's SDK README row was blank without it).TestV3DeleteBillingInvoice, modeled onTestV3UpdateBillingInvoice: pins the customer to a manual-approval billing profile so the standard invoice stays indraft(deletable) instead of auto-advancing, then exercises the delete/re-delete/gathering/unknown-ID cases end-to-end against a live server.Outstanding before merge: the TypeSpec doc-comment change hasn't been run through
make gen-apiyet, soapi/v3/openapi.yamland the generated JS SDK README are still out of sync withoperations.tsp. Want me to run that now?Overview
Fixes #(issue)
Notes for reviewer
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation